/*
 * @Author: 黄威
 * @Date: 2021-07-28 09:09:06
 * @LastEditors: 黄威
 * @LastEditTime: 2022-03-21 11:06:57
 * @Description: 二三维类型映射工具类
 */
import type { Anchor, LabelStyle, MarkerStyle, Style } from 'types'

import L from 'leaflet'
import * as Cesium from 'Cesium'
import Color from 'color'

// 图片缓存，提供图片加载效率
export const imageSourceManager = {
    // 缓存处理
    hashObj: {} as { [key: string]: HTMLImageElement },
    // 获取对象
    get(src: string) {
        return this.hashObj[src]
    },
    // 加载对象
    load(src: string | HTMLImageElement, cb?: (img: HTMLImageElement) => void) {
        let image: HTMLImageElement

        // 如果是个图片
        if (src instanceof HTMLImageElement) {
            this.hashObj[src.src] = src
            image = src
        }
        // 如果存在
        else if (this.get(src)) {
            image = this.get(src)
        }
        // 尝试加载
        else {
            image = new Image()
            image.src = src
            this.hashObj[src] = image
        }
        // 运行图片跨域
        image.crossOrigin = 'anonymous'

        // 检查图片加载状态
        if (image.complete) {
            cb && cb(image)
        } else {
            // 加载成功回调
            const loadedCb = function () {
                cb && cb(image)
                image.removeEventListener('load', loadedCb)
            }
            image.addEventListener('load', loadedCb)
        }

        return image
    },
    // 移除
    remove(key: string) {
        delete this.hashObj[key]
    },
}

// 转角锚点
export const anchorTranslate: { [key in Anchor]: string } = {
    center: 'translate(-50%,-50%)',
    top: 'translate(-50%,0)',
    'top-left': 'translate(0,0)',
    'top-right': 'translate(-100%,0)',
    bottom: 'translate(-50%,-100%)',
    'bottom-left': 'translate(0,-100%)',
    'bottom-right': 'translate(-100%,-100%)',
    left: 'translate(0,-50%)',
    right: 'translate(-100%,-50%)',
}

// 默认样式（二维，三维对标二维）
const defStyle: Style = {
    // 边线
    stroke: true,
    weight: 3,

    // 颜色
    color: '#3388ff',
    opacity: 1,

    // 边线样式
    lineCap: 'round',
    lineJoin: 'round',
    dashArray: null,

    // 多边形样式
    fill: false,
    fillColor: null,
    fillOpacity: 0.2,

    // 点要素相关
    radius: 10,
}

// 默认marker样式
const defMarkerStyle: MarkerStyle = {
    opacity: 1,
    img: '',
    anchor: 'bottom',
    size: null,
    offset: [0, 0],
}

// 默认label样式
const defLabelStyle: LabelStyle = {
    text: '',
    textAlign: 'left',

    // 字体相关
    fontColor: '#000',
    fontOpacity: 1,
    fontSize: '14px',
    fontFamily: '微软雅黑',

    // 背景色相关
    fill: false,
    fillColor: '#fff',
    fillOpacity: 0.7,

    // 盒子相关
    padding: [5, 7],

    // border相关
    border: false,
    borderColor: '#000',
    borderOpacity: 1,
    borderStyle: 'solid',
    borderWidth: 1,
    borderRadius: 0,

    anchor: 'bottom',
    offset: [0, 0],
}

// 三维 dash样式工具
function dashStringToBits(dashPatternString: string) {
    const dashPatternLength = dashPatternString.length
    let dashPatternNumber = 0
    let currentBitValue = 65536
    for (let i = 0; i < 16; ++i) {
        currentBitValue = currentBitValue >> 1
        const charPos = Math.floor((i / 16) * dashPatternLength)
        const char = dashPatternString.charAt(charPos)
        if (char !== ' ') {
            dashPatternNumber |= currentBitValue
        }
    }
    return dashPatternNumber
}
// 三维 边线和填充色
function parseStyleColor(style: Style) {
    const color = Cesium.Color.fromCssColorString(style.color!)
    color.alpha = style.opacity!

    let fillColor
    if (style.fill) {
        fillColor = Cesium.Color.fromCssColorString(
            (style.fillColor || style.color)!
        )
        fillColor.alpha = style.fillOpacity!
    }

    return { color, fillColor }
}
// 通用颜色转换
function colorToRgba(color: string, opacity: number = 1) {
    return Color(color).alpha(opacity).toString()
}
// 设置锚点和偏移
function setAnchorAndOffset2D(
    div: HTMLElement,
    anchor?: Anchor,
    offset?: number | [number, number] | null
) {
    // 偏移
    if (typeof offset === 'number') {
        offset = [offset, offset]
    }
    if (offset) {
        div.style.marginLeft = -offset[0] + 'px'
        div.style.marginTop = -(offset[1] ?? 0) + 'px'
    }

    // 锚点
    if (!(anchor! in anchorTranslate)) {
        anchor = 'center'
    }
    div.style.transform = anchorTranslate[anchor!]
}
function setAnchorAndOffset3D(
    g:
        | Cesium.BillboardGraphics
        | Cesium.LabelGraphics
        | Cesium.Billboard
        | Cesium.Label,
    anchor?: Anchor,
    offset?: number | [number, number] | null
) {
    // 偏移
    if (typeof offset === 'number') {
        offset = [offset, offset]
    }
    g.pixelOffset = new Cesium.ConstantProperty(
        offset
            ? new Cesium.Cartesian2(-(offset[0] ?? 0), -(offset[1] ?? 0))
            : new Cesium.Cartesian2(0, 0)
    )

    // 锚点
    let h, v

    const anchorArr = anchor!.split('-')
    if (anchorArr.includes('left')) h = Cesium.HorizontalOrigin.LEFT
    else if (anchorArr.includes('right')) h = Cesium.HorizontalOrigin.RIGHT
    else h = Cesium.HorizontalOrigin.CENTER

    if (anchorArr.includes('top')) v = Cesium.VerticalOrigin.TOP
    else if (anchorArr.includes('bottom')) v = Cesium.VerticalOrigin.BOTTOM
    else v = Cesium.VerticalOrigin.CENTER

    g.horizontalOrigin = new Cesium.ConstantProperty(h)
    g.verticalOrigin = new Cesium.ConstantProperty(v)
}
// 绑定其他三维要素样式
function bindOthers(g: any, defStyle: any, style: any) {
    for (const key in style) {
        if (!(key in defStyle) && key in g) {
            g[key] = style[key]
        }
    }
}

// 二维 自定义 MarkerIcon
const MarkerIcon = L.Icon.extend({
    options: {
        // opacity: 1,
        // img: false,
        // anchor: 'bottom',
        // size: [16, 16],
        // offset: [0, 0],
        className: 'leaflet-graphic-icon',
    },

    createIcon: function (oldIcon: any) {
        const div =
            oldIcon && oldIcon.tagName === 'DIV'
                ? oldIcon
                : document.createElement('div')

        const options = this.options

        div.innerHTML = options.img
            ? `<img src="${options.img}" />`
            : '<div class="leflet-div-icon"></div>'

        this._setIconStyles(div, 'icon')

        return div
    },

    createShadow: function () {
        return null
    },

    _setIconStyles: function (div: HTMLDivElement, name: string) {
        const options: MarkerStyle = this.options
        const img = div.children[0] as HTMLElement

        // 一些默认的样式
        div.style.opacity = String(options.opacity ?? 1)
        div.className = `leaflet-marker-${name} ${
            (options as any).className || ''
        }`
        div.style.pointerEvents = 'none'
        img.style.pointerEvents = 'auto'

        // 大小
        let size = options.size
        if (typeof size === 'number') {
            size = [size, size]
        }
        if (size) {
            img.style.width = size[0] + 'px'
            img.style.height = size[1] + 'px'
        }

        // 设置锚点
        setAnchorAndOffset2D(img, options.anchor, options.offset)
    },
}) as new (style: MarkerStyle) => L.Icon

// 二维 自定义 LabelIcon
const LabelIcon = L.Icon.extend({
    options: {
        className: 'leaflet-graphic-icon',
    },

    createIcon: function (oldIcon: any) {
        const div =
            oldIcon && oldIcon.tagName === 'DIV'
                ? oldIcon
                : document.createElement('div')

        const options = this.options

        div.innerHTML = options.text
            ? `<pre>${options.text /* .replace(/\n/gi, '<br/>') */}</pre>`
            : '<div class="leflet-div-icon"></div>'

        this._setIconStyles(div, 'icon')

        return div
    },

    createShadow: function () {
        return null
    },

    _setIconStyles: function (div: HTMLDivElement, name: string) {
        const options: LabelStyle = this.options
        const p = div.children[0] as HTMLElement

        // 一些默认的样式
        div.className = `leaflet-marker-${name} ${
            (options as any).className || ''
        }`
        div.style.pointerEvents = 'none'

        // 设置样式
        p.style.pointerEvents = 'auto'
        p.style.width = 'max-content'
        p.style.lineHeight = '1'
        p.style.textAlign = options.textAlign!

        // 字体
        p.style.color = colorToRgba(options.fontColor!, options.fontOpacity!)
        p.style.fontSize = options.fontSize!
        p.style.fontFamily = options.fontFamily!

        // 背景色
        if (options.fill) {
            p.style.backgroundColor = colorToRgba(
                options.fillColor!,
                options.fillOpacity!
            )
        }

        // 容器
        let padding = options.padding
        if (typeof padding === 'number') {
            padding = [padding, padding]
        }
        if (padding) {
            p.style.padding = `${padding[0]}px ${padding[1]}px`
        }

        // border
        if (options.border) {
            p.style.borderWidth = (options.borderWidth || 0) + 'px'
            p.style.borderColor = colorToRgba(
                options.borderColor!,
                options.borderOpacity!
            )

            p.style.borderStyle = options.borderStyle!
            p.style.borderRadius = (options.borderRadius || 0) + 'px'
        }

        // 设置锚点
        setAnchorAndOffset2D(p, options.anchor, options.offset)
    },
}) as new (style: MarkerStyle) => L.Icon

// 2维样式构造器
const StyleSetters2D = {
    default(feature: L.Path, style: Style) {
        feature.setStyle(style as L.PathOptions)
    },
    Marker(feature: L.Marker, style: MarkerStyle) {
        feature.setIcon(new MarkerIcon(style))
    },
    Label(feature: L.Marker, style: LabelStyle) {
        feature.setIcon(new LabelIcon(style))
    },
}
Object.assign(StyleSetters2D, {
    Point: StyleSetters2D.default,
    Polyline: StyleSetters2D.default,
    Polygon: StyleSetters2D.default,
    Rectangle: StyleSetters2D.default,
    Circle: StyleSetters2D.default,
})

// 3维样式构造器
const StyleSetters3D = {
    Point(entity: Cesium.Entity, style: Style) {
        const point = entity.point
        const { color, fillColor } = parseStyleColor(style)

        // 边线
        if (style.stroke) {
            point.outlineColor = new Cesium.ConstantProperty(color)
            point.outlineWidth = new Cesium.ConstantProperty(style.weight)
        } else {
            point.outlineWidth = new Cesium.ConstantProperty(0)
        }

        // 填充
        if (style.fill) {
            point.color = new Cesium.ConstantProperty(fillColor)
            point.pixelSize = new Cesium.ConstantProperty(style.radius! * 2)
        } else {
            point.pixelSize = new Cesium.ConstantProperty(0)
        }

        // 绑定其他要素
        bindOthers(point, defStyle, style)
    },
    Polyline(entity: Cesium.Entity, style: Style) {
        const polyline = entity.polyline

        polyline.show = new Cesium.ConstantProperty(style.stroke!)
        if (!style.stroke) return

        const { color } = parseStyleColor(style)

        // 虚线
        if (style.dashArray) {
            const arr = new Array(style.dashArray[1]).fill('-')
            arr.fill(' ', 0, style.dashArray[0])

            polyline.material = new Cesium.PolylineDashMaterialProperty({
                color: color,
                dashLength: new Cesium.ConstantProperty(style.dashArray[1]),
                dashPattern: new Cesium.ConstantProperty(
                    dashStringToBits(arr.join(''))
                ),
            })
        }
        // 实线
        else {
            polyline.material = new Cesium.ColorMaterialProperty(color)
        }

        // 线宽
        polyline.width = style.weight!

        // lineCap 和 lineJoin 三维暂不支持

        // 绑定其他要素
        bindOthers(polyline, defStyle, style)
    },
    Polygon(entity: Cesium.Entity, style: Style) {
        // 边线
        this.Polyline(entity, style)

        const polygon = entity.polygon

        // 可见性
        polygon.show = new Cesium.ConstantProperty(style.fill!)
        if (!style.fill) return

        // 填充色
        const { fillColor } = parseStyleColor(style)
        polygon.material = new Cesium.ColorMaterialProperty(fillColor)

        // 绑定其他要素
        bindOthers(polygon, defStyle, style)
    },
    Marker(entity: Cesium.Entity, style: MarkerStyle) {
        const billboard = entity.billboard

        imageSourceManager.load(style.img!, image => {
            billboard.image = new Cesium.ConstantProperty(image)
        })

        let { size } = style
        // 大小
        if (typeof size === 'number') {
            size = [size, size]
        }
        if (size) {
            billboard.width = new Cesium.ConstantProperty(size[0])
            billboard.height = new Cesium.ConstantProperty(size[1])
        }

        setAnchorAndOffset3D(billboard, style.anchor || 'center', style.offset!)

        // 绑定其他要素
        bindOthers(billboard, defMarkerStyle, style)
    },
    Label(entity: Cesium.Entity, style: LabelStyle) {
        const label = entity.label

        // 字体
        label.text = new Cesium.ConstantProperty(
            String(style.text!) /* .replace(/<br ?\/?>/gi, '\n') */
        )
        label.font = style.fontSize + ' ' + style.fontFamily
        label.fillColor = Cesium.Color.fromCssColorString(
            colorToRgba(style.fontColor!, style.fontOpacity!)
        )

        // 背景色
        label.showBackground = new Cesium.ConstantProperty(true)
        label.backgroundColor = new Cesium.ConstantProperty(
            style.fill
                ? Cesium.Color.fromCssColorString(
                      colorToRgba(style.fillColor!, style.fillOpacity!)
                  )
                : Cesium.Color.TRANSPARENT
        )

        // padding设置（需要总是showBackground = true）
        let padding = style.padding
        if (typeof padding === 'number') {
            padding = [padding, padding]
        }
        ;(label as any).backgroundPadding = padding
            ? new Cesium.Cartesian2(padding[1], padding[0])
            : undefined

        /*
        // 边线颜色（字体边线）
        if (style.border) {
            label.outlineColor = Cesium.Color.fromCssColorString(
                colorToRgba(style.borderColor!, style.borderOpacity!)
            )
            label.outlineWidth = style.borderWidth!
        } else {
            label.outlineColor = Cesium.Color.TRANSPARENT
            label.outlineWidth = 0
        }

        // 显示模式
        label.style = (
            style.border
                ? Cesium.LabelStyle.FILL_AND_OUTLINE
                : Cesium.LabelStyle.FILL
        ) as any
        */

        setAnchorAndOffset3D(label, style.anchor || 'center', style.offset!)

        // 绑定其他要素
        bindOthers(label, defLabelStyle, style)
    },
    Circle(entity: Cesium.Entity, style: Style) {
        const ellipse = entity.ellipse

        const { color, fillColor } = parseStyleColor(style)

        // 边线
        ellipse.outline = !!style.stroke!
        if (style.stroke) {
            ellipse.outlineColor = color
            ellipse.outlineWidth = style.weight!
        }

        // 填充
        ellipse.fill = !!style.fill!
        if (style.fill) {
            ellipse.material = new Cesium.ColorMaterialProperty(fillColor)
        }

        // 绑定其他要素
        bindOthers(ellipse, defStyle, style)
    },
}
Object.assign(StyleSetters3D, { Rectangle: StyleSetters3D.Polygon })

// 设置样式（自定义样式映射到实际图上对象） --- 基础矢量数据（点线面）
export function setStyle(
    type: string,
    style: Style | MarkerStyle | LabelStyle,
    feature: Cesium.Entity | L.Layer
) {
    let _defStyle = {}
    // 基础 点线面 --- 补全一些默认属性
    if (type !== 'Marker' && type !== 'Label') {
        _defStyle = Object.assign({}, defStyle, { fill: type !== 'Polyline' })
    }
    // marker类型
    else if (type === 'Marker') {
        _defStyle = defMarkerStyle
    }
    // label类型
    else if (type === 'Label') {
        _defStyle = defLabelStyle
    }
    // 设置 _style默认值
    if (!('_style' in feature)) {
        ;(feature as any)._style = {}
    }
    ;(feature as any)._style = Object.assign({}, _defStyle, style)

    // 设置样式
    const Setters = feature instanceof L.Layer ? StyleSetters2D : StyleSetters3D

    if (type in Setters) {
        ;(Setters as any)[type](feature, (feature as any)._style)
    }
}

// 序列化样式（图上样式转自定义样式） --- todo
export function parseStyle(type: string, feature: any) {}
